package com.by.datasource.handler;

import cn.hutool.core.lang.ClassScanner;
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import com.by.datasource.datapermission.DataPermissionContext;
import com.by.datasource.datapermission.DataPermissionSupport;
import com.by.datasource.datapermission.processor.DataPermissionProcessor;
import com.by.toolkit.utils.ByIfNotNullUtil;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.FromItem;
import net.sf.jsqlparser.statement.select.Join;
import net.sf.jsqlparser.statement.select.PlainSelect;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ByDataPermissionHandler implements DataPermissionHandler {

    private DataPermissionContext dataPermissionContext;

    private DataPermissionSupport dataPermissionSupport;

    private static final List<DataPermissionProcessor> PROCESSORS = Lists.newArrayList();

    static {
        Set<Class<?>> classes = ClassScanner.scanPackageBySuper("com.by.datasource", DataPermissionProcessor.class);
        PROCESSORS.addAll(classes.stream()
                .map(c -> (DataPermissionProcessor) ReflectUtil.newInstance(c))
                .collect(Collectors.toList()));
    }

    public ByDataPermissionHandler injectDpSupport() {
        PROCESSORS.forEach(p -> p.dpSupport(dataPermissionSupport));
        return this;
    }

    public void processDataPermission(PlainSelect plainSelect, String mappedStatementId) {
        FromItem fromItem = plainSelect.getFromItem();
        Expression unionWhereExpression = this.unionExpression(plainSelect.getWhere(), this.createExpression(fromItem));
        plainSelect.setWhere(unionWhereExpression);

        List<Join> joins = plainSelect.getJoins();
        if (CollectionUtils.isEmpty(joins)) {
            return;
        }
        joins.forEach(join -> {
            join.getOnExpressions().addAll(this.createExpression(join.getRightItem()));
        });
    }

    public Expression unionExpression(Expression expression, List<Expression> expressions) {
        if (CollectionUtils.isEmpty(expressions)) {
            return expression;
        }
        for (Expression e : expressions) {
            expression = Objects.nonNull(expression) ? new AndExpression(expression, e) : e;
        }
        return expression;
    }

    public List<Expression> createExpression(FromItem fromItem) {
        if (Objects.isNull(fromItem)) {
            return Collections.emptyList();
        }
        if (!(fromItem instanceof Table)) {
            return Collections.emptyList();
        }
        Table table = (Table) fromItem;
        String name = table.getName();
        Alias alias = table.getAlias();
        String aliasName = ByIfNotNullUtil.getIfNotNull(alias, () -> alias.getName(), null);

        // 表不需要权限
        if (!dataPermissionContext.dataPermissionFlag(name.toLowerCase())) {
            return Collections.emptyList();
        }

        return PROCESSORS.stream()
                .map(processor -> this.inExpression(aliasName, processor))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    public InExpression inExpression(String aliasName, DataPermissionProcessor processor) {
        if (Objects.isNull(processor)) {
            return null;
        }
        List<Long> dataIds = processor.dataIds();
        if (CollectionUtils.isEmpty(dataIds)) {
            return null;
        }
        String columnName = processor.columnName();
        InExpression inExpression = new InExpression();
        inExpression.setLeftExpression(this.newColumn(aliasName, columnName));
        inExpression.setRightItemsList(new ExpressionList(dataIds.stream()
                .map(LongValue::new)
                .collect(Collectors.toList())));
        return inExpression;
    }

    public Column newColumn(String alias, String columnName) {
        return new Column(StringUtils.isNotBlank(alias) ? alias.concat(StringPool.DOT).concat(columnName) : columnName);
    }

    @Override
    public Expression getSqlSegment(Expression where, String mappedStatementId) {
        return null;
    }

}
